import { useControllableValue } from 'ahooks';
import { Modal } from 'antd';
import type { ModalProps } from 'antd/lib/modal';
import React, {
  forwardRef,
  ForwardRefRenderFunction,
  useCallback,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react';
import ReactDOM from 'react-dom';

interface SModalProps extends ModalProps {
  children: React.ReactNode;
}

const SModal: React.FC<SModalProps> = ({
  children,
  okButtonProps,
  onOk,
  ...props
}) => {
  const [loadingProxy, setLoadingProxy] = useState(false);

  const onOkProxy = (...params: any[]) => {
    if (onOk) {
      const p: any = (onOk as any)(...params);
      if (p && typeof p === 'object' && 'then' in p) {
        setLoadingProxy(true);
        p.finally(() => {
          setLoadingProxy(false);
        });
      }
    }
  };

  const okButtonPropsProxy = {
    loading: loadingProxy,
    ...(okButtonProps || {}),
  };

  return (
    <Modal onOk={onOkProxy} okButtonProps={okButtonPropsProxy} {...props}>
      {children}
    </Modal>
  );
};

export const useControlVisible = (props: any = {}) => {
  const [visible, setVisible] = useControllableValue(props, {
    defaultValue: false,
    defaultValuePropName: 'defaultVisible',
    valuePropName: 'visible',
    trigger: 'setVisible',
  });

  return [visible, setVisible] as const;
};

interface WithModalAppProps {
  _clear?: () => void;
  visible?: boolean;
  setVisible?: (flag: boolean, ...params: any[]) => void;
  clear?: () => void;
  [key: string]: any;
}

const withModalApp = (
  Component: React.ComponentType<any>,
  { autoClear = true } = {},
) => {
  const App: ForwardRefRenderFunction<any, WithModalAppProps> = (
    { _clear: clear, ...props },
    ref,
  ) => {
    const timer = useRef<NodeJS.Timeout | null>(null);
    const [visible, setVis] = useControlVisible(props);
    const setVisible = (flag: boolean, ...params: any[]) => {
      setVis(flag, ...params);

      if (!autoClear) return;
      // 关闭窗口1s后，自动清理
      if (timer.current) clearTimeout(timer.current);
      if (!flag && clear) {
        timer.current = setTimeout(clear, 1000);
      }
    };

    useImperativeHandle(ref, () => ({
      clear,
      visible,
      setVisible,
    }));

    return (
      <Component
        {...props}
        visible={visible}
        setVisible={setVisible}
        clear={clear}
      />
    );
  };

  return forwardRef(App);
};

export const createModalMount = (Component: React.ComponentType<any>) => {
  const App = withModalApp(Component);

  const mount = async ({ ...props }: any) => {
    const container = document.createElement('div');
    document.body.appendChild(container);
    const clear = () => {
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
    };

    const p = new Promise<any>((resolve, reject) => {
      try {
        ReactDOM.render(
          <App {...props} _clear={clear} ref={(r: any) => resolve(r)} />,
          container,
        );
      } catch (err) {
        reject(err);
      }
    });

    return p;
  };

  const show = async (props: any = {}) => {
    const model = await mount(props);
    if (model && typeof model.setVisible === 'function') {
      model.setVisible(true);
    }
    return model;
  };

  return { show };
};

export const useModalMount = (
  Component: React.ComponentType<any>,
  { renderSave = true } = {},
) => {
  const modelRef = useRef<any>(null);
  const App = useMemo(
    () => withModalApp(Component, { autoClear: !renderSave }),
    [Component, renderSave],
  );

  const mount = useCallback(async (props: any = {}) => {
    const container = document.createElement('div');
    document.body.appendChild(container);
    const clear = () => {
      ReactDOM.unmountComponentAtNode(container);
      document.body.removeChild(container);
      modelRef.current = undefined;
    };

    const p = new Promise((resolve) => {
      ReactDOM.render(
        <App {...props} _clear={clear} ref={(r: any) => resolve(r)} />,
        container,
      );
    });

    return p;
  }, []);

  const show = useCallback(
    async (props: any = {}) => {
      if (renderSave) {
        if (!modelRef.current) {
          modelRef.current = await mount(props);
        }
      } else {
        modelRef.current = await mount(props);
      }

      if (
        modelRef.current &&
        typeof modelRef.current.setVisible === 'function'
      ) {
        modelRef.current.setVisible(true);
      }

      return modelRef.current;
    },
    [mount, renderSave],
  );

  return [show, modelRef] as const;
};

interface WithModalControlsProps {
  triggerRender?: (
    controls: { visible: boolean; setVisible: (flag: boolean) => void },
    props: any,
  ) => React.ReactNode;
  visible?: boolean;
  setVisible?: (flag: boolean) => void;
  [key: string]: any;
}

export const withModalControls = (Component: React.ComponentType<any>) => {
  return ({ triggerRender, ...props }: WithModalControlsProps) => {
    const [visible, setVisible] = useControlVisible(props);

    return (
      <>
        {typeof triggerRender === 'function' &&
          triggerRender(
            { visible, setVisible: setVisible as (flag: boolean) => void },
            props,
          )}
        <Component {...props} visible={visible} setVisible={setVisible} />
      </>
    );
  };
};

/**
 * @description 可以直接使用组件放到页面中
 */
export const useComponentModal = (
  Component: React.ComponentType<any>,
  props: any = {},
) => {
  const [temporaryProps, setTemporaryProps] = useState<any>({});
  const [visible, setVisible] = useControlVisible(props);
  const modalCounter = (
    <Component
      visible={visible}
      setVisible={setVisible}
      {...props}
      {...(temporaryProps || {})}
    />
  );
  return {
    modalCounter,
    show: (p: any) => {
      setTemporaryProps(p);
      setVisible(true);
    },
    close: () => {
      setTemporaryProps({});
      setVisible(false);
    },
    visible,
    setVisible,
  };
};

export default SModal;
